<template>
  <component
    :is="component"
    v-bind="$props"
    :class="[
      'gantt-node',
      {
        moving: dragData.dragging || resizeData.resizing,
        'is-month-view': isMonthView,
        focusing,
        hovering,
      },
    ]"
    :style="style"
    @drag-start="onDragStart"
    @resize-start="onResizeStart"
    @focus-start="startFocus"
    @hover-start="startHover"
    @hover-end="endHover"
  />
</template>
<script lang="ts">
import Vue, { PropType } from 'vue';
import GanttGroup from './gantt-group.vue';
import GanttLeaf from './gantt-leaf.vue';
import GanttMilestone from './gantt-milestone.vue';
import { GanttLayoutNode, Bus, ColUnit } from '@/utils/types';
import { isGroup, isMilestone } from '@/utils';

interface Style {
  transform: string;
  width: string;
  height: string;
}

function dividedBy(offsetX: number, colW: number) {
  return Math.round(offsetX / colW);
}

export default Vue.extend({
  name: 'GanttNode',
  props: {
    data: {
      type: Object as PropType<GanttLayoutNode>,
      required: true,
    },
    bus: {
      type: Object as PropType<Bus>,
      required: true,
    },
  },
  data: () => ({
    dragData: {
      dragging: false,
      offsetX: 0,
    },
    resizeData: {
      resizing: false,
      offsetX: 0,
    },
    focusing: false,
    hovering: false,
    otherWorking: false,
  }),
  computed: {
    component(): Vue.VueConstructor {
      if (isGroup(this.data)) return GanttGroup;
      if (isMilestone(this.data)) return GanttMilestone;
      return GanttLeaf;
    },
    dataInPx(): { x: number; y: number; w: number; h: number } {
      const { rowH, colW } = this.bus;
      const { x, y, w, h } = this.data;
      const { dragData, resizeData } = this;
      // 里程碑中心需要对齐当天最右边，而非中间
      const milestoneOffset = isMilestone(this.data) ? colW / 2 : 0;

      return {
        x: x * colW + dragData.offsetX + milestoneOffset,
        y: y * rowH,
        w: Math.max(colW, w * colW + resizeData.offsetX),
        h: h * rowH,
      };
    },
    style(): Style {
      const { x, y, w, h } = this.dataInPx;
      return {
        transform: `translate(${x}px, ${y}px)`,
        width: w + 'px',
        height: h + 'px',
      };
    },
    absolutePosition() {
      let { x, y } = (this as any).dataInPx;
      const { rowH } = this.bus;
      let node: any = this.$parent;
      // 统计到顶级 layout 的距离
      while (node.$options.name !== 'GanttChart') {
        if (node.$options.name === 'GanttLayout') {
          y += rowH;
        } else if (node.$options.name === 'GanttNode') {
          x += node.dataInPx.x;
          y += node.dataInPx.y;
        }
        node = node.$parent;
      }

      return { x, y };
    },
    isMonthView() {
      return this.bus.colUnit === ColUnit.Month;
    },
  },
  created() {
    const { ee } = this.bus;
    ee.on(ee.Event.ScrollToNode, (id: string) => {
      if (id !== this.data.id) return;
      const { x, y } = this.absolutePosition;
      ee.emit(ee.Event.ScrollTo, { x: x - 200, y: y - 150 });
      this.startFocus();
    });
    ee.on(ee.Event.Focus, (id: string) => {
      if (id !== this.data.id) {
        this.blur();
      } else {
        this.focus();
      }
    });

    ee.on(ee.Event.DragStart, ({ id }: { id: string }) => {
      this.otherWorking = id !== this.data.id;
    });
    ee.on(ee.Event.DragEnd, () => {
      this.otherWorking = false;
    });

    ee.on(ee.Event.ResizeStart, ({ id }: { id: string }) => {
      this.otherWorking = id !== this.data.id;
    });
    ee.on(ee.Event.ResizeEnd, () => {
      this.otherWorking = false;
    });
  },
  methods: {
    onDragStart() {
      this.dragData.dragging = true;
      this.dragData.offsetX = 0;
      const { ee } = this.bus;
      ee.emit(ee.Event.DragStart, { id: this.data.id });
      document.addEventListener('mousemove', this.onDrag);
      document.addEventListener('mouseup', this.onDragEnd);
      document.documentElement.classList.add('grabbing');
    },
    onDrag(e: MouseEvent) {
      this.dragData.offsetX += e.movementX;
      const { ee } = this.bus;
      ee.emit(ee.Event.Drag, {
        movedCols: dividedBy(this.dragData.offsetX, this.bus.colW),
        dataInPx: {
          ...this.dataInPx,
          ...this.absolutePosition,
        },
      });
    },
    onDragEnd() {
      document.removeEventListener('mousemove', this.onDrag);
      document.removeEventListener('mouseup', this.onDragEnd);
      document.documentElement.classList.remove('grabbing');
      this.dragData.dragging = false;
      this.dragData.offsetX = 0;
      const { ee } = this.bus;
      ee.emit(ee.Event.DragEnd);
    },
    onResizeStart() {
      this.resizeData.resizing = true;
      this.resizeData.offsetX = 0;
      const { ee } = this.bus;
      ee.emit(ee.Event.ResizeStart, { id: this.data.id });
      document.addEventListener('mousemove', this.onResize);
      document.addEventListener('mouseup', this.onResizeEnd);
      document.documentElement.classList.add('col-resizing');
    },
    onResize(e: MouseEvent) {
      this.resizeData.offsetX += e.movementX;

      const { ee } = this.bus;
      ee.emit(ee.Event.Resize, {
        resizedCols: Math.max(
          1 - this.data.w, // 至少一列
          dividedBy(this.resizeData.offsetX, this.bus.colW),
        ),
        dataInPx: this.dataInPx,
      });
    },
    onResizeEnd() {
      document.removeEventListener('mousemove', this.onResize);
      document.removeEventListener('mouseup', this.onResizeEnd);
      document.documentElement.classList.remove('col-resizing');
      this.resizeData.resizing = false;
      this.resizeData.offsetX = 0;
      const { ee } = this.bus;
      ee.emit(ee.Event.ResizeEnd);
    },
    /**
     * 通知其他节点 blur & 自身 focus
     */
    startFocus() {
      const { ee } = this.bus;
      ee.emit(ee.Event.Focus, this.data.id);
    },
    startHover() {
      // 仅 month 视图开启悬浮显示日期
      if (this.bus.colUnit !== ColUnit.Month) return;

      // 其他节点正在 drag 或者 resize 的时候，不能 hover
      if (this.otherWorking) return;

      const { ee } = this.bus;
      ee.emit(ee.Event.StartHover, {
        id: this.data.id,
        w: this.dataInPx.w,
        ...this.absolutePosition,
      });

      this.hovering = true;
    },
    endHover() {
      const { ee } = this.bus;
      ee.emit(ee.Event.EndHover);

      this.hovering = false;
    },
    focus() {
      this.focusing = true;
      // document.addEventListener('click', this.blur);
    },
    blur() {
      this.focusing = false;
      document.removeEventListener('click', this.blur);
    },
  },
});
</script>
<style lang="less" scoped>
@import '../assets/vars';

.gantt-node {
  will-change: transform;
  position: absolute;
  transition: transform 0.3s ease-in-out, width 0.3s ease-in-out; // 与 el-tree 相同

  &.moving {
    transition: unset;
  }

  &.moving,
  &.focusing,
  &.hovering {
    &::before {
      content: '';
      position: absolute;
      top: 0;
      right: 0;
      bottom: 0;
      left: 0;
      background-color: fade(@progress-background, 30%);
      border: 1px solid fade(@progress-content, 80%);
    }
  }
}
</style>
<style>
.grabbing * {
  cursor: grabbing !important;
}

.col-resizing * {
  cursor: col-resize !important;
}
</style>
